// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/password_manager/core/browser/store_metrics_reporter.h"
#include <memory>
#include <utility>

#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/ranges/algorithm.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "components/password_manager/core/browser/affiliation/affiliation_utils.h"
#include "components/password_manager/core/browser/password_feature_manager.h"
#include "components/password_manager/core/browser/password_manager_features_util.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "components/password_manager/core/browser/password_manager_util.h"
#include "components/password_manager/core/browser/password_reuse_detector.h"
#include "components/password_manager/core/browser/password_reuse_manager.h"
#include "components/password_manager/core/browser/password_store_consumer.h"
#include "components/password_manager/core/browser/password_sync_util.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/sync/base/features.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "google_apis/gaia/gaia_urls.h"

namespace password_manager {

namespace {

// Minimum time between two metrics reporting trigger for the same profile. This
// is needed in order to avoid unnecessarily reporting password store metrics on
// every profile creation (which is very frequent on Android for example).
constexpr base::TimeDelta kMetricsReportingThreshold = base::Days(1);

// Common prefix for all histograms.
constexpr char kPasswordManager[] = "PasswordManager";

// Need to stay in sync with the PasswordType variant in histograms.xml.
constexpr char kAutoGeneratedSuffix[] = ".AutoGenerated";
constexpr char kUserCreatedSuffix[] = ".UserCreated";
constexpr char kOverallSuffix[] = ".Overall";

// Need to stay in sync with the CustomPassphraseStatus variant in
// histograms.xml.
constexpr char kWithCustomPassphraseSuffix[] = ".WithCustomPassphrase";
constexpr char kWithoutCustomPassphraseSuffix[] = ".WithoutCustomPassphrase";

bool IsCustomPassphraseEnabled(password_manager::SyncState sync_state) {
  switch (sync_state) {
    case password_manager::SyncState::kSyncingWithCustomPassphrase:
    case password_manager::SyncState::
        kAccountPasswordsActiveWithCustomPassphrase:
      return true;
    case password_manager::SyncState::kNotSyncing:
    case password_manager::SyncState::kSyncingNormalEncryption:
    case password_manager::SyncState::kAccountPasswordsActiveNormalEncryption:
      return false;
  }
  NOTREACHED_NORETURN();
}

base::StringPiece GetCustomPassphraseSuffix(bool custom_passphrase_enabled) {
  return custom_passphrase_enabled ? kWithCustomPassphraseSuffix
                                   : kWithoutCustomPassphraseSuffix;
}

// Returns a suffix (infix, really) to be used in histogram names to
// differentiate the profile store from the account store. Need to stay in sync
// with the Store variant in histograms.xml.
base::StringPiece GetMetricsSuffixForStore(bool is_account_store) {
  return is_account_store ? ".AccountStore" : ".ProfileStore";
}

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class SyncingAccountState {
  kSyncingAndSyncPasswordNotSaved = 0,
  kSyncingAndSyncPasswordSaved = 1,
  kNotSyncingAndSyncPasswordNotSaved = 2,
  kNotSyncingAndSyncPasswordSaved = 3,
  kMaxValue = kNotSyncingAndSyncPasswordSaved,
};

void LogAccountStatHiRes(const std::string& name, int sample) {
  base::UmaHistogramCustomCounts(name, sample, 0, 1000, 100);
}

void LogNumberOfAccountsForScheme(base::StringPiece suffix_for_store,
                                  const std::string& scheme,
                                  int sample) {
  base::UmaHistogramCustomCounts(
      base::StrCat({kPasswordManager, suffix_for_store,
                    ".TotalAccountsHiRes3.WithScheme.", scheme}),
      sample, 1, 1000, 100);
}

void LogTimesUsedStat(const std::string& name, int sample) {
  base::UmaHistogramCustomCounts(name, sample, 0, 100, 10);
}

void ReportNumberOfAccountsMetrics(
    bool is_account_store,
    bool custom_passphrase_enabled,
    const std::vector<std::unique_ptr<PasswordForm>>& forms) {
  base::flat_map<std::tuple<std::string, PasswordForm::Type, int>, int>
      accounts_per_site_map;

  for (const auto& form : forms) {
    accounts_per_site_map[{form->signon_realm, form->type,
                           form->blocked_by_user}]++;
  }

  base::StringPiece store_suffix = GetMetricsSuffixForStore(is_account_store);
  base::StringPiece custom_passphrase_suffix =
      GetCustomPassphraseSuffix(custom_passphrase_enabled);

  int total_user_created_accounts = 0;
  int total_generated_accounts = 0;
  int blocklisted_sites = 0;
  for (const auto& pair : accounts_per_site_map) {
    PasswordForm::Type password_type = std::get<1>(pair.first);
    int blocklisted = std::get<2>(pair.first);
    int accounts_per_site = pair.second;
    if (blocklisted) {
      ++blocklisted_sites;
      continue;
    }

    constexpr base::StringPiece kAccountsPerSiteSuffix =
        ".AccountsPerSiteHiRes3";

    if (password_type == PasswordForm::Type::kGenerated) {
      total_generated_accounts += accounts_per_site;
      LogAccountStatHiRes(
          base::StrCat({kPasswordManager, store_suffix, kAccountsPerSiteSuffix,
                        kAutoGeneratedSuffix, custom_passphrase_suffix}),
          accounts_per_site);
    } else {
      total_user_created_accounts += accounts_per_site;
      LogAccountStatHiRes(
          base::StrCat({kPasswordManager, store_suffix, kAccountsPerSiteSuffix,
                        kUserCreatedSuffix, custom_passphrase_suffix}),
          accounts_per_site);
    }

    LogAccountStatHiRes(
        base::StrCat({kPasswordManager, store_suffix, kAccountsPerSiteSuffix,
                      kOverallSuffix, custom_passphrase_suffix}),
        accounts_per_site);
  }

  static constexpr base::StringPiece kTotalAccountsByTypeSuffix =
      ".TotalAccountsHiRes3.ByType";

  LogAccountStatHiRes(
      base::StrCat({kPasswordManager, store_suffix, kTotalAccountsByTypeSuffix,
                    kUserCreatedSuffix, custom_passphrase_suffix}),
      total_user_created_accounts);

  LogAccountStatHiRes(
      base::StrCat({kPasswordManager, store_suffix, kTotalAccountsByTypeSuffix,
                    kAutoGeneratedSuffix, custom_passphrase_suffix}),
      total_generated_accounts);

  LogAccountStatHiRes(
      base::StrCat({kPasswordManager, store_suffix, kTotalAccountsByTypeSuffix,
                    kOverallSuffix, custom_passphrase_suffix}),
      total_user_created_accounts + total_generated_accounts);

  LogAccountStatHiRes(
      base::StrCat({kPasswordManager, store_suffix, ".BlacklistedSitesHiRes3",
                    custom_passphrase_suffix}),
      blocklisted_sites);
}

void ReportLoginsWithSchemesMetrics(
    bool is_account_store,
    const std::vector<std::unique_ptr<PasswordForm>>& forms) {
  int android_logins = 0;
  int ftp_logins = 0;
  int http_logins = 0;
  int https_logins = 0;
  int other_logins = 0;

  for (const auto& form : forms) {
    if (form->blocked_by_user)
      continue;

    if (IsValidAndroidFacetURI(form->signon_realm)) {
      ++android_logins;
    } else if (form->url.SchemeIs(url::kHttpsScheme)) {
      ++https_logins;
    } else if (form->url.SchemeIs(url::kHttpScheme)) {
      ++http_logins;
    } else if (form->url.SchemeIs(url::kFtpScheme)) {
      ++ftp_logins;
    } else {
      ++other_logins;
    }
  }

  base::StringPiece suffix_for_store =
      GetMetricsSuffixForStore(is_account_store);

  LogNumberOfAccountsForScheme(suffix_for_store, "Android", android_logins);
  LogNumberOfAccountsForScheme(suffix_for_store, "Ftp", ftp_logins);
  LogNumberOfAccountsForScheme(suffix_for_store, "Http", http_logins);
  LogNumberOfAccountsForScheme(suffix_for_store, "Https", https_logins);
  LogNumberOfAccountsForScheme(suffix_for_store, "Other", other_logins);
}

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class PasswordManagerEnableState {
  kDefault = 0,
  kEnabledByUser = 1,
  kEnabledByExtension = 2,
  kEnabledByPolicy = 3,
  kEnabledByRecommendedPolicy = 4,
  kEnabledByOther = 5,
  kDisabledByUser = 6,
  kDisabledByExtension = 7,
  kDisabledByPolicy = 8,
  kDisabledByRecommendedPolicy = 9,
  kDisabledByOther = 10,
  kMaxValue = kDisabledByOther,
};

PasswordManagerEnableState
CredentialsEnableServiceSettingToPasswordManagerEnableState(
    const PrefService::Preference* pref) {
  DCHECK(pref->GetValue()->GetIfBool().has_value());

  if (pref->IsDefaultValue()) {
    return PasswordManagerEnableState::kDefault;
  }

  bool credentials_service_enabled = pref->GetValue()->GetBool();
  if (pref->IsUserControlled()) {
    return credentials_service_enabled
               ? PasswordManagerEnableState::kEnabledByUser
               : PasswordManagerEnableState::kDisabledByUser;
  }
  if (pref->IsManaged()) {
    return credentials_service_enabled
               ? PasswordManagerEnableState::kEnabledByPolicy
               : PasswordManagerEnableState::kDisabledByPolicy;
  }
  if (pref->IsExtensionControlled()) {
    return credentials_service_enabled
               ? PasswordManagerEnableState::kEnabledByExtension
               : PasswordManagerEnableState::kDisabledByExtension;
  }
  if (pref->IsRecommended()) {
    return credentials_service_enabled
               ? PasswordManagerEnableState::kEnabledByRecommendedPolicy
               : PasswordManagerEnableState::kDisabledByRecommendedPolicy;
  }
  return credentials_service_enabled
             ? PasswordManagerEnableState::kEnabledByOther
             : PasswordManagerEnableState::kDisabledByOther;
}

void ReportPasswordNotesMetrics(
    bool is_account_store,
    const std::vector<std::unique_ptr<PasswordForm>>& forms) {
  if (!base::FeatureList::IsEnabled(syncer::kPasswordNotesWithBackup)) {
    return;
  }

  base::StringPiece suffix_for_store =
      GetMetricsSuffixForStore(is_account_store);

  int credentials_with_non_empty_notes_count =
      base::ranges::count_if(forms, [](const auto& form) {
        return base::ranges::any_of(
            form->notes, [](const auto& note) { return !note.value.empty(); });
      });

  base::UmaHistogramCounts1000(
      base::StrCat({kPasswordManager, suffix_for_store,
                    ".PasswordNotes.CountCredentialsWithNonEmptyNotes2"}),
      credentials_with_non_empty_notes_count);

  const std::string histogram_name =
      base::StrCat({kPasswordManager, suffix_for_store,
                    ".PasswordNotes.CountNotesPerCredential3"});
  base::ranges::for_each(forms, [histogram_name](const auto& form) {
    if (!form->notes.empty()) {
      base::UmaHistogramCounts100(histogram_name, form->notes.size());
    }
  });
}

void ReportTimesPasswordUsedMetrics(
    bool is_account_store,
    bool custom_passphrase_enabled,
    const std::vector<std::unique_ptr<PasswordForm>>& forms) {
  base::StringPiece store_suffix = GetMetricsSuffixForStore(is_account_store);
  base::StringPiece custom_passphrase_suffix =
      GetCustomPassphraseSuffix(custom_passphrase_enabled);

  for (const auto& form : forms) {
    auto type = form->type;
    const int times_used_in_html_form = form->times_used_in_html_form;

    static constexpr base::StringPiece kTimesPasswordUsedSuffix =
        ".TimesPasswordUsed3";

    if (type == PasswordForm::Type::kGenerated) {
      LogTimesUsedStat(
          base::StrCat({kPasswordManager, store_suffix,
                        kTimesPasswordUsedSuffix, kAutoGeneratedSuffix,
                        custom_passphrase_suffix}),
          times_used_in_html_form);
    } else {
      LogTimesUsedStat(
          base::StrCat({kPasswordManager, store_suffix,
                        kTimesPasswordUsedSuffix, kUserCreatedSuffix,
                        custom_passphrase_suffix}),
          times_used_in_html_form);
    }
    LogTimesUsedStat(
        base::StrCat({kPasswordManager, store_suffix, kTimesPasswordUsedSuffix,
                      kOverallSuffix, custom_passphrase_suffix}),
        times_used_in_html_form);
  }
}

void ReportSyncingAccountStateMetrics(
    const std::string& sync_username,
    const std::vector<std::unique_ptr<PasswordForm>>& forms) {
  const GURL gaia_signon_realm =
      GaiaUrls::GetInstance()->gaia_origin().GetURL();
  bool syncing_account_saved = base::ranges::any_of(
      forms, [&gaia_signon_realm, &sync_username](const auto& form) {
        return gaia_signon_realm == GURL(form->signon_realm) &&
               gaia::AreEmailsSame(sync_username,
                                   base::UTF16ToUTF8(form->username_value));
      });
  SyncingAccountState sync_account_state =
      sync_username.empty()
          ? (syncing_account_saved
                 ? SyncingAccountState::kNotSyncingAndSyncPasswordSaved
                 : SyncingAccountState::kNotSyncingAndSyncPasswordNotSaved)
          : (syncing_account_saved
                 ? SyncingAccountState::kSyncingAndSyncPasswordSaved
                 : SyncingAccountState::kSyncingAndSyncPasswordNotSaved);
  base::UmaHistogramEnumeration(
      base::StrCat({kPasswordManager, ".SyncingAccountState3"}),
      sync_account_state);
}

void ReportDuplicateCredentialsMetrics(
    const std::vector<std::unique_ptr<PasswordForm>>& forms) {
  // First group the passwords by [signon_realm, username] (which should be a
  // unique identifier).
  std::map<std::pair<std::string, std::u16string>, std::vector<std::u16string>>
      passwords_by_realm_and_user;
  for (const auto& form : forms) {
    passwords_by_realm_and_user[std::make_pair(form->signon_realm,
                                               form->username_value)]
        .push_back(form->password_value);
  }
  // Now go over the passwords by [realm, username] - typically there should
  // be only one password each.
  size_t credentials_with_duplicates = 0;
  size_t credentials_with_mismatched_duplicates = 0;
  for (auto& entry : passwords_by_realm_and_user) {
    std::vector<std::u16string>& passwords = entry.second;
    // Only one password -> no duplicates, move on.
    if (passwords.size() == 1)
      continue;
    std::sort(passwords.begin(), passwords.end());
    auto last = std::unique(passwords.begin(), passwords.end());
    // If |last| moved from |.end()|, that means there were duplicate
    // passwords.
    if (last != passwords.end())
      credentials_with_duplicates++;
    // If there is more than 1 password left after de-duping, then there were
    // mismatched duplicates.
    if (std::distance(passwords.begin(), last) > 1)
      credentials_with_mismatched_duplicates++;
  }

  base::UmaHistogramCustomCounts(
      base::StrCat({kPasswordManager, ".CredentialsWithDuplicates3"}),
      credentials_with_duplicates, 0, 32, 6);
  base::UmaHistogramCustomCounts(
      base::StrCat({kPasswordManager, ".CredentialsWithMismatchedDuplicates3"}),
      credentials_with_mismatched_duplicates, 0, 32, 6);
}

void ReportPasswordIssuesMetrics(
    BulkCheckDone bulk_check_done,
    const std::vector<std::unique_ptr<PasswordForm>>& forms) {
  int count_leaked = base::ranges::count_if(forms, [](const auto& form) {
    return form->password_issues.contains(InsecureType::kLeaked);
  });
  base::UmaHistogramCounts100(
      base::StrCat({kPasswordManager, ".CompromisedCredentials3.CountLeaked"}),
      count_leaked);
  if (bulk_check_done) {
    base::UmaHistogramCounts100(
        base::StrCat({kPasswordManager,
                      ".CompromisedCredentials3.CountLeakedAfterBulkCheck"}),
        count_leaked);
  }

  int count_phished = base::ranges::count_if(forms, [](const auto& form) {
    return form->password_issues.contains(InsecureType::kPhished);
  });
  base::UmaHistogramCounts100(
      base::StrCat({kPasswordManager, ".CompromisedCredentials3.CountPhished"}),
      count_phished);
}

void ReportPasswordProtectedMetrics(
    const std::vector<std::unique_ptr<PasswordForm>>& forms) {
  for (const std::unique_ptr<PasswordForm>& form : forms) {
    if (!form->blocked_by_user && form->password_value.size() > 0) {
      metrics_util::LogIsPasswordProtected(
          form->password_value.size() >=
          password_manager::kMinPasswordLengthToCheck);
    }
  }
}

void ReportStoreMetrics(bool is_account_store,
                        bool custom_passphrase_enabled,
                        const std::string& sync_username,
                        BulkCheckDone bulk_check_done,
                        bool is_safe_browsing_enabled,
                        std::vector<std::unique_ptr<PasswordForm>> results) {
  ReportNumberOfAccountsMetrics(is_account_store, custom_passphrase_enabled,
                                results);
  ReportLoginsWithSchemesMetrics(is_account_store, results);
  ReportTimesPasswordUsedMetrics(is_account_store, custom_passphrase_enabled,
                                 results);
  ReportPasswordNotesMetrics(is_account_store, results);
  if (is_safe_browsing_enabled) {
    ReportPasswordProtectedMetrics(results);
  }

  // The remaining metrics are not recorded for the account store:
  // - SyncingAccountState2 just doesn't make sense, since syncing users only
  // use
  //   the profile store.
  // - DuplicateCredentials *could* be recorded for the account store, but are
  //   not very critical.
  // - Compromised credentials are only stored in the profile store.
  if (is_account_store) {
    return;
  }

  ReportSyncingAccountStateMetrics(sync_username, results);
  ReportDuplicateCredentialsMetrics(results);
  ReportPasswordIssuesMetrics(bulk_check_done, results);
}

void ReportMultiStoreMetrics(
    std::unique_ptr<std::map<std::pair<std::string, std::u16string>,
                             std::u16string>> profile_store_results,
    std::unique_ptr<std::map<std::pair<std::string, std::u16string>,
                             std::u16string>> account_store_results,
    bool is_opted_in) {
  // Count the contents of the account store as compared to the profile store:
  // - Additional:  Credentials that are in the account store, but not in the
  //                profile store.
  // - Missing:     Credentials that are in the profile store, but not in the
  //                account store.
  // - Identical:   Credentials that are in both stores.
  // - Conflicting: Credentials with the same signon realm and username, but
  //                different passwords in the two stores.
  int additional = 0;
  int missing = 0;
  int identical = 0;
  int conflicting = 0;

  // Go over the data from both stores in parallel, always advancing in the
  // one that is "behind". The entries are sorted by signon_realm and
  // username (the exact ordering doesn't matter, just that it's consistent).
  auto profile_it = profile_store_results->begin();
  auto account_it = account_store_results->begin();
  while (account_it != account_store_results->end()) {
    // First, go over any entries in the profile store that don't exist in the
    // account store.
    while (profile_it != profile_store_results->end() &&
           profile_it->first < account_it->first) {
      ++missing;
      ++profile_it;
    }
    // Now profile_it->first is >= account_it->first.
    // Check if they match.
    if (profile_it != profile_store_results->end() &&
        account_it->first == profile_it->first) {
      // The signon_realm and username match, check the password value.
      if (account_it->second == profile_it->second)
        ++identical;
      else
        ++conflicting;

      ++profile_it;
    } else {
      // The signon_realm and username don't match, so this is an account
      // store entry that doesn't exist in the profile store.
      ++additional;
    }

    ++account_it;
  }
  // We're done with the account store. Go over any remaining profile store
  // entries.
  while (profile_it != profile_store_results->end()) {
    ++missing;
    ++profile_it;
  }

  if (is_opted_in) {
    base::UmaHistogramCounts100(
        base::StrCat(
            {kPasswordManager, ".AccountStoreVsProfileStore4.Additional"}),
        additional);
    base::UmaHistogramCounts100(
        base::StrCat(
            {kPasswordManager, ".AccountStoreVsProfileStore4.Missing"}),
        missing);
    base::UmaHistogramCounts100(
        base::StrCat(
            {kPasswordManager, ".AccountStoreVsProfileStore4.Identical"}),
        identical);
    base::UmaHistogramCounts100(
        base::StrCat(
            {kPasswordManager, ".AccountStoreVsProfileStore4.Conflicting"}),
        conflicting);
  }
}

void ReportAllMetrics(bool custom_passphrase_enabled,
                      const std::string& sync_username,
                      BulkCheckDone bulk_check_done,
                      bool is_opted_in_account_storage,
                      bool is_safe_browsing_enabled,
                      absl::optional<std::vector<std::unique_ptr<PasswordForm>>>
                          profile_store_results,
                      absl::optional<std::vector<std::unique_ptr<PasswordForm>>>
                          account_store_results) {
  // Maps from (signon_realm, username) to password.
  std::unique_ptr<
      std::map<std::pair<std::string, std::u16string>, std::u16string>>
      profile_store_passwords_per_signon_and_username;
  std::unique_ptr<
      std::map<std::pair<std::string, std::u16string>, std::u16string>>
      account_store_passwords_per_signon_and_username;

  if (profile_store_results.has_value()) {
    profile_store_passwords_per_signon_and_username = std::make_unique<
        std::map<std::pair<std::string, std::u16string>, std::u16string>>();
    for (const std::unique_ptr<PasswordForm>& form :
         profile_store_results.value()) {
      profile_store_passwords_per_signon_and_username->insert(std::make_pair(
          std::make_pair(form->signon_realm, form->username_value),
          form->password_value));
    }
  }

  if (account_store_results.has_value()) {
    account_store_passwords_per_signon_and_username = std::make_unique<
        std::map<std::pair<std::string, std::u16string>, std::u16string>>();
    for (const std::unique_ptr<PasswordForm>& form :
         account_store_results.value()) {
      account_store_passwords_per_signon_and_username->insert(std::make_pair(
          std::make_pair(form->signon_realm, form->username_value),
          form->password_value));
    }
  }

  if (profile_store_results.has_value()) {
    ReportStoreMetrics(/*is_account_store=*/false, custom_passphrase_enabled,
                       sync_username, bulk_check_done, is_safe_browsing_enabled,
                       std::move(profile_store_results).value());
  }
  if (account_store_results.has_value()) {
    ReportStoreMetrics(/*is_account_store=*/true, custom_passphrase_enabled,
                       sync_username, bulk_check_done, is_safe_browsing_enabled,
                       std::move(account_store_results).value());
  }

  // If both stores exist, kick off the MultiStoreMetricsReporter.
  if (profile_store_passwords_per_signon_and_username &&
      account_store_passwords_per_signon_and_username) {
    ReportMultiStoreMetrics(
        std::move(profile_store_passwords_per_signon_and_username),
        std::move(account_store_passwords_per_signon_and_username),
        is_opted_in_account_storage);
  }
}

void OnMetricsReportingCompleted(
    base::WeakPtr<StoreMetricsReporter> reporter_weak_ptr,
    base::OnceClosure done_callback) {
  // Metrics reporting is performed asynchronously on a background thread. By
  // the time metrics reporting is completed, it could be the case that the
  // StoreMetricsReporter has been destructed already (e.g. if the user closes
  // the browser profile for which metrics are being reported). If the reporter
  // doesn't exist anymore, it's pointless (and wrong) to run the
  // `done_callback` since the main purpose of the `done_callback` is to
  // destroy the reporter (as in password_store_utils.cc).
  if (reporter_weak_ptr) {
    std::move(done_callback).Run();
  }
}

void ReportBiometricAuthenticationBeforeFillingMetrics(PrefService* prefs) {
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
  base::UmaHistogramBoolean(
      base::StrCat({kPasswordManager, ".BiometricAuthBeforeFillingEnabled2"}),
      prefs->GetBoolean(
          password_manager::prefs::kBiometricAuthenticationBeforeFilling));
#endif
}

}  // namespace

StoreMetricsReporter::StoreMetricsReporter(
    PasswordStoreInterface* profile_store,
    PasswordStoreInterface* account_store,
    const syncer::SyncService* sync_service,
    const signin::IdentityManager* identity_manager,
    PrefService* prefs,
    password_manager::PasswordReuseManager* password_reuse_manager,
    bool is_under_advanced_protection,
    base::OnceClosure done_callback)
    : profile_store_(profile_store),
      account_store_(account_store),
      is_under_advanced_protection_(is_under_advanced_protection),
      done_callback_(std::move(done_callback)) {
  DCHECK(prefs);

  base::TimeDelta time_since_last_metrics_reporting =
      base::Time::Now() -
      base::Time::FromTimeT(prefs->GetDouble(
          password_manager::prefs::kLastTimePasswordStoreMetricsReported));
  if (time_since_last_metrics_reporting < kMetricsReportingThreshold) {
    // Upon constructing StoreMetricsReporter, it's moved into member variable
    // in StoreMetricReporterHelper. `done_callback_` effectively destroys
    // StoreMetricReporterHelper. Therefore, `done_callback_` must be called
    // asynchronously to avoid moving the StoreMetricsReporter pointer to a
    // destroyed unique_ptr.
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, std::move(done_callback_));
    return;
  }

  prefs->SetDouble(
      password_manager::prefs::kLastTimePasswordStoreMetricsReported,
      base::Time::Now().ToDoubleT());

  sync_username_ =
      password_manager::sync_util::GetSyncUsernameIfSyncingPasswords(
          sync_service, identity_manager);

  custom_passphrase_enabled_ = IsCustomPassphraseEnabled(
      password_manager_util::GetPasswordSyncState(sync_service));

  bulk_check_done_ =
      BulkCheckDone(prefs->HasPrefPath(prefs::kLastTimePasswordCheckCompleted));

  is_opted_in_account_storage_ =
      features_util::IsOptedInForAccountStorage(prefs, sync_service);

  is_safe_browsing_enabled_ = safe_browsing::IsSafeBrowsingEnabled(*prefs);

  base::UmaHistogramEnumeration(
      base::StrCat({kPasswordManager, ".EnableState"}),
      CredentialsEnableServiceSettingToPasswordManagerEnableState(
          prefs->FindPreference(
              password_manager::prefs::kCredentialsEnableService)));

  ReportBiometricAuthenticationBeforeFillingMetrics(prefs);

  // May be null in tests.
  if (profile_store) {
    if (password_reuse_manager) {
      password_reuse_manager->ReportMetrics(sync_username_,
                                            is_under_advanced_protection_);
    }
  }

  if (profile_store_)
    profile_store_->GetAllLogins(weak_ptr_factory_.GetWeakPtr());

  if (account_store_)
    account_store_->GetAllLogins(weak_ptr_factory_.GetWeakPtr());

  if (!profile_store_ && !account_store_) {
    // There is nothing else to report.
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, std::move(done_callback_));
  }
}

void StoreMetricsReporter::OnGetPasswordStoreResults(
    std::vector<std::unique_ptr<PasswordForm>> results) {
  // This class overrides OnGetPasswordStoreResultsFrom() (the version of this
  // method that also receives the originating store), so the store-less version
  // never gets called.
  NOTREACHED();
}

void StoreMetricsReporter::OnGetPasswordStoreResultsFrom(
    PasswordStoreInterface* store,
    std::vector<std::unique_ptr<PasswordForm>> results) {
  if (store == account_store_) {
    account_store_results_ = std::move(results);
  } else {
    profile_store_results_ = std::move(results);
  }

  // Wait until all expected results are available before starting metrics
  // reporting.
  if ((profile_store_ && !profile_store_results_) ||
      (account_store_ && !account_store_results_)) {
    return;
  }

  DCHECK(done_callback_);

  base::ThreadPool::PostTaskAndReply(
      FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
      base::BindOnce(&ReportAllMetrics, custom_passphrase_enabled_,
                     sync_username_, bulk_check_done_,
                     is_opted_in_account_storage_, is_safe_browsing_enabled_,
                     std::exchange(profile_store_results_, absl::nullopt),
                     std::exchange(account_store_results_, absl::nullopt)),
      base::BindOnce(&OnMetricsReportingCompleted,
                     weak_ptr_factory_.GetWeakPtr(),
                     std::move(done_callback_)));
}

StoreMetricsReporter::~StoreMetricsReporter() = default;

}  // namespace password_manager
